package com.wechat.pay.contrib.apache.httpclient.auth;

import com.wechat.pay.contrib.apache.httpclient.HeadersName;
import com.wechat.pay.contrib.apache.httpclient.Validator;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WechatPay2Validator implements Validator {

    private static final Logger log = LoggerFactory.getLogger(WechatPay2Validator.class);

    private Verifier verifier;

    public WechatPay2Validator(Verifier verifier) {
        this.verifier = verifier;
    }

    private static RuntimeException parameterError(String message, Object... args) {
        return new IllegalArgumentException("Response parameter error: " + String.format(message, args));
    }

    private static RuntimeException verifyFail(String message, Object... args) {
        return new IllegalArgumentException("signature verify fail: " + String.format(message, args));
    }

    /**
     * 验证微信应答签名;
     *
     * @param response 应答
     * @return 是否通过
     * @throws IOException 处理entity可能抛出的异常;
     */
    @Override
    public final boolean validate(CloseableHttpResponse response) throws IOException {
        try {
            validateParameters(response);

            String message = buildMessage(response);
            String serial = response.getFirstHeader(HeadersName.WECHATPAY_SERIAL).getValue();
            String signature = response.getFirstHeader(HeadersName.WECHATPAY_SIGNATURE).getValue();

            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]", serial, message, signature, response.getFirstHeader(HeadersName.REQUEST_ID).getValue());
            }
        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }

        return true;
    }

    /**
     * 校验应答header是否全面及合理;
     *
     * @param response 响应
     */
    protected final void validateParameters(CloseableHttpResponse response) {
        String requestId;
        if (!response.containsHeader(HeadersName.REQUEST_ID)) {
            throw parameterError("empty Request-ID");
        } else {
            requestId = response.getFirstHeader(HeadersName.REQUEST_ID).getValue();
        }

        if (!response.containsHeader(HeadersName.WECHATPAY_SERIAL)) {
            throw parameterError("empty Wechatpay-Serial, request-id=[%s]", requestId);
        } else if (!response.containsHeader(HeadersName.WECHATPAY_SIGNATURE)) {
            throw parameterError("empty Wechatpay-Signature, request-id=[%s]", requestId);
        } else if (!response.containsHeader(HeadersName.WECHATPAY_TIMESTAMP)) {
            throw parameterError("empty Wechatpay-Timestamp, request-id=[%s]", requestId);
        } else if (!response.containsHeader(HeadersName.WECHATPAY_NONCE)) {
            throw parameterError("empty Wechatpay-Nonce, request-id=[%s]", requestId);
        } else {
            String timestamp = response.getFirstHeader(HeadersName.WECHATPAY_TIMESTAMP).getValue();
            try {
                Instant instant = Instant.ofEpochSecond(Long.parseLong(timestamp));
                // 拒绝5分钟之外的应答
                if (Duration.between(instant, Instant.now()).abs().toMinutes() >= 5) {
                    throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestamp, requestId);
                }
            } catch (DateTimeException | NumberFormatException e) {
                throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestamp, requestId);
            }
        }
    }

    /**
     * 组合验证微信支付签名时需要的消息串, 参考: <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml">签名验证</a>
     *
     * @return 消息串
     * @throws IOException 处理entity时可能会抛出此异常
     */
    protected final String buildMessage(CloseableHttpResponse response) throws IOException {
        String timestamp = response.getFirstHeader(HeadersName.WECHATPAY_TIMESTAMP).getValue();
        String nonce = response.getFirstHeader(HeadersName.WECHATPAY_NONCE).getValue();
        String body = getResponseBody(response);
        return timestamp + "\n" + nonce + "\n" + body + "\n";
    }

    protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
    }
}
